/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <string.h>
#include <errno.h>
#include <assert.h>
#include <os/mynewt.h>
#include <mcu/cmsis_nvic.h>
#include <hal/hal_spi.h>
#include <mcu/nrf5340_net_hal.h>
#include <nrf.h>

#if MYNEWT_VAL(SPI_0_MASTER) || MYNEWT_VAL(SPI_0_SLAVE)

#define SPIM_TXD_MAXCNT_MAX 0xffff

/* Used to disable all interrupts */
#define NRF_SPI_IRQ_DISABLE_ALL 0xffffffff

/*
 *  Slave states
 *
 *  IDLE: Slave not ready to be used. If master attempts to access
 *        slave it will receive the default character
 *  ACQ_SEM: Slave is attempting to acquire semaphore.
 *  READY: Slave is ready for master to send it data
 *
 */
#define HAL_SPI_SLAVE_STATE_IDLE        (0)
#define HAL_SPI_SLAVE_STATE_ACQ_SEM     (1)
#define HAL_SPI_SLAVE_STATE_READY       (2)

#if MYNEWT_VAL(SPI_0_MASTER)
struct nrf5340_net_hal_spi
{
    uint8_t spi_xfr_flag;
    uint8_t dummy_rx;
    uint16_t nhs_buflen;
    uint16_t nhs_bytes_txd;
    struct hal_spi_settings spi_cfg;

    /* Pointers to tx/rx buffers */
    uint8_t *nhs_txbuf;
    uint8_t *nhs_rxbuf;

    /* Callback and arguments */
    hal_spi_txrx_cb txrx_cb_func;
    void            *txrx_cb_arg;
};
#endif

#if MYNEWT_VAL(SPI_0_SLAVE)
struct nrf5340_net_hal_spi
{
    uint8_t slave_state;
    uint16_t nhs_buflen;
    uint16_t nhs_bytes_txd;
    struct hal_spi_settings spi_cfg;

    /* Pointers to tx/rx buffers */
    uint8_t *nhs_txbuf;
    uint8_t *nhs_rxbuf;

    /* Callback and arguments */
    hal_spi_txrx_cb txrx_cb_func;
    void            *txrx_cb_arg;
};
#endif

static struct nrf5340_net_hal_spi nrf5340_net_hal_spi0;

static void
nrf5340_net_spi0_irq_handler(void)
{
    struct nrf5340_net_hal_spi *spi = &nrf5340_net_hal_spi0;
    uint16_t xfr_bytes;
#if MYNEWT_VAL(SPI_0_MASTER)
    uint16_t len;
#endif

    os_trace_isr_enter();

#if MYNEWT_VAL(SPI_0_MASTER)
    if (NRF_SPIM0_NS->EVENTS_END) {
        NRF_SPIM0_NS->EVENTS_END = 0;

        /* Should not occur but if no transfer just leave  */
        if (spi->spi_xfr_flag != 0) {
            /* Are there more bytes to send? */
            xfr_bytes = NRF_SPIM0_NS->TXD.AMOUNT;
            spi->nhs_bytes_txd += xfr_bytes;
            if (spi->nhs_bytes_txd < spi->nhs_buflen) {
                spi->nhs_txbuf += xfr_bytes;
                len = spi->nhs_buflen - spi->nhs_bytes_txd;
                len = min(SPIM_TXD_MAXCNT_MAX, len);
                NRF_SPIM0_NS->TXD.PTR = (uint32_t)spi->nhs_txbuf;
                NRF_SPIM0_NS->TXD.MAXCNT = len;

                /* If no rxbuf, we need to set rxbuf and maxcnt to 1 */
                if (spi->nhs_rxbuf) {
                    spi->nhs_rxbuf += xfr_bytes;
                    NRF_SPIM0_NS->RXD.PTR    = (uint32_t)spi->nhs_rxbuf;
                    NRF_SPIM0_NS->RXD.MAXCNT = len;
                }
                NRF_SPIM0_NS->TASKS_START = 1;
            } else {
                if (spi->txrx_cb_func) {
                    spi->txrx_cb_func(spi->txrx_cb_arg, spi->nhs_buflen);

                }
                spi->spi_xfr_flag = 0;
                NRF_SPIM0_NS->INTENCLR = SPIM_INTENSET_END_Msk;
            }
        }
    }
#endif

#if MYNEWT_VAL(SPI_0_SLAVE)
    /* Semaphore acquired event */
    if (NRF_SPIS0_NS->EVENTS_ACQUIRED) {
        NRF_SPIS0_NS->EVENTS_ACQUIRED = 0;

        if (spi->slave_state == HAL_SPI_SLAVE_STATE_ACQ_SEM) {
            if (spi->nhs_txbuf == NULL) {
                NRF_SPIS0_NS->TXD.PTR = 0;
                NRF_SPIS0_NS->TXD.MAXCNT = 0;
            } else {
                NRF_SPIS0_NS->TXD.PTR = (uint32_t)spi->nhs_txbuf;
                NRF_SPIS0_NS->TXD.MAXCNT = spi->nhs_buflen;
            }

            if (spi->nhs_rxbuf == NULL) {
                NRF_SPIS0_NS->RXD.PTR = 0;
                NRF_SPIS0_NS->RXD.MAXCNT = 0;
            } else {
                NRF_SPIS0_NS->RXD.PTR = (uint32_t)spi->nhs_rxbuf;
                NRF_SPIS0_NS->RXD.MAXCNT = spi->nhs_buflen;
            }
            NRF_SPIS0_NS->TASKS_RELEASE = 1;
            spi->slave_state = HAL_SPI_SLAVE_STATE_READY;
        }
    }

    /* SPI transaction complete */
    if (NRF_SPIS0_NS->EVENTS_END) {
        NRF_SPIS0_NS->EVENTS_END = 0;
        if (spi->slave_state == HAL_SPI_SLAVE_STATE_READY) {
            if (spi->txrx_cb_func) {
                /* Get transfer length */
                if (spi->nhs_txbuf == NULL) {
                    xfr_bytes = NRF_SPIS0_NS->RXD.AMOUNT;
                } else {
                    xfr_bytes = NRF_SPIS0_NS->TXD.AMOUNT;
                }
                spi->txrx_cb_func(spi->txrx_cb_arg, xfr_bytes);
            }
            spi->slave_state = HAL_SPI_SLAVE_STATE_IDLE;
        }
    }
#endif

    os_trace_isr_exit();
}

#if MYNEWT_VAL(SPI_0_MASTER)
static void
hal_spi_master_stop_transfer(void)
{
    NRF_SPIM0_NS->TASKS_STOP = 1;
    while (!NRF_SPIM0_NS->EVENTS_STOPPED) {
    };
    NRF_SPIM0_NS->EVENTS_STOPPED = 0;
}

static int
hal_spi_config_master(struct nrf5340_net_hal_spi *spi,
                      struct hal_spi_settings *settings)
{
    int rc;
    uint32_t nrf_config;
    uint32_t frequency;
    NRF_GPIO_Type *port;
    uint32_t pin;

    memcpy(&spi->spi_cfg, settings, sizeof(*settings));

    /*
     * Configure SCK. NOTE: this is done here in the config API as the data
     * mode is not set at init time so we do it here when we configure the SPI.
     */
    pin = NRF_SPIM0_NS->PSEL.SCK & SPIM_PSEL_SCK_PIN_Msk;
    if (NRF_SPIM0_NS->PSEL.SCK & SPIM_PSEL_SCK_PORT_Msk) {
        port = NRF_P1_NS;
    } else {
        port = NRF_P0_NS;
    }

    if (settings->data_mode <= HAL_SPI_MODE1) {
        port->OUTCLR = (1UL << pin);
    } else {
        port->OUTSET = (1UL << pin);
    }
    port->PIN_CNF[pin] =
        (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) |
        (GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos);

    /* Only 8-bit word sizes supported. */
    rc = 0;
    switch (settings->word_size) {
        case HAL_SPI_WORD_SIZE_8BIT:
            break;
        default:
            rc = EINVAL;
            break;
    }

    switch (settings->data_mode) {
        case HAL_SPI_MODE0:
            nrf_config = (SPIM_CONFIG_CPOL_ActiveHigh << SPIM_CONFIG_CPOL_Pos) |
                         (SPIM_CONFIG_CPHA_Leading << SPIM_CONFIG_CPHA_Pos);
            break;
        case HAL_SPI_MODE1:
            nrf_config = (SPIM_CONFIG_CPOL_ActiveHigh << SPIM_CONFIG_CPOL_Pos) |
                         (SPIM_CONFIG_CPHA_Trailing << SPIM_CONFIG_CPHA_Pos);
            break;
        case HAL_SPI_MODE2:
            nrf_config = (SPIM_CONFIG_CPOL_ActiveLow << SPIM_CONFIG_CPOL_Pos) |
                         (SPIM_CONFIG_CPHA_Leading << SPIM_CONFIG_CPHA_Pos);
            break;
        case HAL_SPI_MODE3:
            nrf_config = (SPIM_CONFIG_CPOL_ActiveLow << SPIM_CONFIG_CPOL_Pos) |
                         (SPIM_CONFIG_CPHA_Trailing << SPIM_CONFIG_CPHA_Pos);
            break;
        default:
            nrf_config = 0;
            rc = EINVAL;
            break;
    }

    /* NOTE: msb first is 0 so no check done */
    if (settings->data_order == HAL_SPI_LSB_FIRST) {
        nrf_config |= SPIM_CONFIG_ORDER_LsbFirst;
    }
    NRF_SPIM0_NS->CONFIG = nrf_config;

    switch (settings->baudrate) {
        case 125:
            frequency = SPIM_FREQUENCY_FREQUENCY_K125;
            break;
        case 250:
            frequency = SPIM_FREQUENCY_FREQUENCY_K250;
            break;
        case 500:
            frequency = SPIM_FREQUENCY_FREQUENCY_K500;
            break;
        case 1000:
            frequency = SPIM_FREQUENCY_FREQUENCY_M1;
            break;
        case 2000:
            frequency = SPIM_FREQUENCY_FREQUENCY_M2;
            break;
        case 4000:
            frequency = SPIM_FREQUENCY_FREQUENCY_M4;
            break;
        case 8000:
            frequency = SPIM_FREQUENCY_FREQUENCY_M8;
            break;
        default:
            frequency = 0;
            rc = EINVAL;
            break;
    }
    NRF_SPIM0_NS->FREQUENCY = frequency;

    return rc;
}
#endif

#if MYNEWT_VAL(SPI_0_SLAVE)
static int
hal_spi_config_slave(struct nrf5340_net_hal_spi *spi,
                     struct hal_spi_settings *settings)
{
    int rc;
    uint32_t nrf_config;

    rc = 0;
    switch (settings->data_mode) {
        case HAL_SPI_MODE0:
            nrf_config = (SPIS_CONFIG_CPOL_ActiveHigh << SPIS_CONFIG_CPOL_Pos) |
                         (SPIS_CONFIG_CPHA_Leading << SPIS_CONFIG_CPHA_Pos);
            break;
        case HAL_SPI_MODE1:
            nrf_config = (SPIS_CONFIG_CPOL_ActiveHigh << SPIS_CONFIG_CPOL_Pos) |
                         (SPIS_CONFIG_CPHA_Trailing << SPIS_CONFIG_CPHA_Pos);
            break;
        case HAL_SPI_MODE2:
            nrf_config = (SPIS_CONFIG_CPOL_ActiveLow << SPIS_CONFIG_CPOL_Pos) |
                         (SPIS_CONFIG_CPHA_Leading << SPIS_CONFIG_CPHA_Pos);
            break;
        case HAL_SPI_MODE3:
            nrf_config = (SPIS_CONFIG_CPOL_ActiveLow << SPIS_CONFIG_CPOL_Pos) |
                         (SPIS_CONFIG_CPHA_Trailing << SPIS_CONFIG_CPHA_Pos);
            break;
        default:
            nrf_config = 0;
            rc = EINVAL;
            break;
    }

    if (settings->data_order == HAL_SPI_LSB_FIRST) {
        nrf_config |= SPIS_CONFIG_ORDER_LsbFirst;
    }
    NRF_SPIS0_NS->CONFIG = nrf_config;

    /* Only 8-bit word sizes supported. */
    switch (settings->word_size) {
        case HAL_SPI_WORD_SIZE_8BIT:
            break;
        default:
            rc = EINVAL;
            break;
    }

    return rc;
}
#endif

#if MYNEWT_VAL(SPI_0_MASTER)
static int
hal_spi_init_master(struct nrf5340_net_hal_spi *spi,
                    struct nrf5340_net_hal_spi_cfg *cfg)
{
    NRF_GPIO_Type *port;
    uint32_t pin;

    /*  Configure MOSI */
    port = HAL_GPIO_PORT(cfg->mosi_pin);
    pin = HAL_GPIO_INDEX(cfg->mosi_pin);
    port->OUTCLR = (1UL << pin);
    port->PIN_CNF[pin] =
        ((uint32_t)GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) |
        ((uint32_t)GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos);

    /* Configure MISO */
    port = HAL_GPIO_PORT(cfg->miso_pin);
    pin = HAL_GPIO_INDEX(cfg->miso_pin);
    port->PIN_CNF[pin] =
        ((uint32_t)GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) |
        ((uint32_t)GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos);

    NRF_SPIM0_NS->PSEL.SCK = cfg->sck_pin;
    NRF_SPIM0_NS->PSEL.MOSI = cfg->mosi_pin;
    NRF_SPIM0_NS->PSEL.MISO = cfg->miso_pin;

    NRF_SPIM0_NS->INTENCLR = NRF_SPI_IRQ_DISABLE_ALL;
    NVIC_SetVector(SPIM0_SPIS0_TWIM0_TWIS0_UARTE0_IRQn,
                   (uint32_t)nrf5340_net_spi0_irq_handler);
    NVIC_SetPriority(SPIM0_SPIS0_TWIM0_TWIS0_UARTE0_IRQn,
                     (1 << __NVIC_PRIO_BITS) - 1);
    NVIC_ClearPendingIRQ(SPIM0_SPIS0_TWIM0_TWIS0_UARTE0_IRQn);
    NVIC_EnableIRQ(SPIM0_SPIS0_TWIM0_TWIS0_UARTE0_IRQn);

    return 0;
}
#endif

#if MYNEWT_VAL(SPI_0_SLAVE)
static int
hal_spi_init_slave(struct nrf5340_net_hal_spi *spi,
                   struct nrf5340_net_hal_spi_cfg *cfg)
{
    NRF_GPIO_Type *port;
    uint32_t pin;

    /* NOTE: making this pin an input is correct! See datasheet */
    port = HAL_GPIO_PORT(cfg->miso_pin);
    pin = HAL_GPIO_INDEX(cfg->miso_pin);
    port->PIN_CNF[pin] =
        ((uint32_t)GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) |
        ((uint32_t)GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos);

    port = HAL_GPIO_PORT(cfg->mosi_pin);
    pin = HAL_GPIO_INDEX(cfg->mosi_pin);
    port->PIN_CNF[pin] =
        ((uint32_t)GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) |
        ((uint32_t)GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos);

    port = HAL_GPIO_PORT(cfg->ss_pin);
    pin = HAL_GPIO_INDEX(cfg->ss_pin);
    port->PIN_CNF[pin] =
        ((uint32_t)GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) |
        ((uint32_t)GPIO_PIN_CNF_PULL_Pullup  << GPIO_PIN_CNF_PULL_Pos) |
        ((uint32_t)GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos);

    port = HAL_GPIO_PORT(cfg->sck_pin);
    pin = HAL_GPIO_INDEX(cfg->sck_pin);
    port->PIN_CNF[pin] =
        ((uint32_t)GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) |
        ((uint32_t)GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos);

    NRF_SPIS0_NS->PSEL.SCK  = cfg->sck_pin;
    NRF_SPIS0_NS->PSEL.MOSI = cfg->mosi_pin;
    NRF_SPIS0_NS->PSEL.MISO = cfg->miso_pin;
    NRF_SPIS0_NS->PSEL.CSN  = cfg->ss_pin;

    /* Disable interrupt and clear any interrupt events */
    NRF_SPIS0_NS->INTENCLR = SPIS_INTENSET_ACQUIRED_Msk | SPIS_INTENSET_END_Msk;
    NRF_SPIS0_NS->EVENTS_END = 0;
    NRF_SPIS0_NS->EVENTS_ACQUIRED = 0;

    /* Enable END_ACQUIRE shortcut. */
    NRF_SPIS0_NS->SHORTS = SPIS_SHORTS_END_ACQUIRE_Msk;

    /* Set interrupt vector and enable IRQ */
    NVIC_SetVector(SPIM0_SPIS0_TWIM0_TWIS0_UARTE0_IRQn,
                   (uint32_t)nrf5340_net_spi0_irq_handler);
    NVIC_SetPriority(SPIM0_SPIS0_TWIM0_TWIS0_UARTE0_IRQn,
                     (1 << __NVIC_PRIO_BITS) - 1);
    NVIC_ClearPendingIRQ(SPIM0_SPIS0_TWIM0_TWIS0_UARTE0_IRQn);
    NVIC_EnableIRQ(SPIM0_SPIS0_TWIM0_TWIS0_UARTE0_IRQn);

    return 0;
}
#endif

/**
 * Initialize the SPI, given by spi_num.
 *
 * @param spi_num The number of the SPI to initialize
 * @param cfg HW/MCU specific configuration,
 *            passed to the underlying implementation, providing extra
 *            configuration.
 * @param spi_type SPI type (master or slave)
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_init(int spi_num, void *cfg, uint8_t spi_type)
{
    struct nrf5340_net_hal_spi *spi = &nrf5340_net_hal_spi0;

    /* Check for valid arguments */
    if (spi_num != 0 || cfg == NULL) {
       return EINVAL;
    }

#if MYNEWT_VAL(SPI_0_MASTER)
    if (spi_type != HAL_SPI_TYPE_MASTER) {
        return EINVAL;
    }

    hal_spi_disable(spi_num);

    return hal_spi_init_master(spi, (struct nrf5340_net_hal_spi_cfg *)cfg);
#endif

#if MYNEWT_VAL(SPI_0_SLAVE)
    if (spi_type != HAL_SPI_TYPE_SLAVE) {
        return EINVAL;
    }

    hal_spi_disable(spi_num);

    return hal_spi_init_slave(spi, (struct nrf5340_net_hal_spi_cfg *)cfg);
#endif
}

int
hal_spi_init_hw(uint8_t spi_num, uint8_t spi_type,
                const struct hal_spi_hw_settings *cfg)
{
    struct nrf5340_net_hal_spi_cfg hal_cfg;

    hal_cfg.sck_pin = cfg->pin_sck;
    hal_cfg.mosi_pin = cfg->pin_mosi;
    hal_cfg.miso_pin = cfg->pin_miso;
    hal_cfg.ss_pin = cfg->pin_ss;

    return hal_spi_init(spi_num, &hal_cfg, spi_type);
}

/**
 * Configure the spi. Must be called after the spi is initialized (after
 * hal_spi_init is called) and when the spi is disabled (user must call
 * hal_spi_disable if the spi has been enabled through hal_spi_enable prior
 * to calling this function). Can also be used to reconfigure an initialized
 * SPI (assuming it is disabled as described previously).
 *
 * @param spi_num The number of the SPI to configure.
 * @param psettings The settings to configure this SPI with
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_config(int spi_num, struct hal_spi_settings *settings)
{
    struct nrf5340_net_hal_spi *spi = &nrf5340_net_hal_spi0;

    if (spi_num != 0) {
        return EINVAL;
    }

#if MYNEWT_VAL(SPI_0_MASTER)
    if (NRF_SPIM0_NS->ENABLE != 0) {
        return EINVAL;
    }

    return hal_spi_config_master(spi, settings);
#endif

#if MYNEWT_VAL(SPI_0_SLAVE)
    if (NRF_SPIS0_NS->ENABLE != 0) {
        return EINVAL;
    }

    return hal_spi_config_slave(spi, settings);
#endif
}

/**
 * Enables the SPI. This does not start a transmit or receive operation;
 * it is used for power mgmt. Cannot be called when a SPI transfer is in
 * progress.
 *
 * @param spi_num
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_enable(int spi_num)
{
    struct nrf5340_net_hal_spi *spi = &nrf5340_net_hal_spi0;

    if (spi_num != 0 || spi->txrx_cb_func == NULL) {
        return EINVAL;
    }

#if MYNEWT_VAL(SPI_0_MASTER)
    NRF_SPIM0_NS->EVENTS_END = 0;
    NRF_SPIM0_NS->ENABLE = (SPIM_ENABLE_ENABLE_Enabled << SPIM_ENABLE_ENABLE_Pos);
#endif

#if MYNEWT_VAL(SPI_0_SLAVE)
    NRF_SPIS0_NS->EVENTS_END = 0;
    NRF_SPIS0_NS->EVENTS_ACQUIRED = 0;
    NRF_SPIS0_NS->INTENSET = SPIS_INTENSET_END_Msk | SPIS_INTENSET_ACQUIRED_Msk;
    NRF_SPIS0_NS->ENABLE = (SPIS_ENABLE_ENABLE_Enabled << SPIS_ENABLE_ENABLE_Pos);
#endif

    return 0;
}

/**
 * Disables the SPI. Used for power mgmt. It will halt any current SPI transfers
 * in progress.
 *
 * @param spi_num
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_disable(int spi_num)
{
    struct nrf5340_net_hal_spi *spi = &nrf5340_net_hal_spi0;

    if (spi_num != 0) {
        return -EINVAL;
    }

#if MYNEWT_VAL(SPI_0_MASTER)
    NRF_SPIM0_NS->INTENCLR = NRF_SPI_IRQ_DISABLE_ALL;

    if (spi->spi_xfr_flag) {
        hal_spi_master_stop_transfer();
        spi->spi_xfr_flag = 0;
    }
    NRF_SPIM0_NS->ENABLE = 0;
#endif

#if MYNEWT_VAL(SPI_0_SLAVE)
    NRF_SPIS0_NS->INTENCLR = NRF_SPI_IRQ_DISABLE_ALL;
    NRF_SPIS0_NS->EVENTS_END = 0;
    NRF_SPIS0_NS->EVENTS_ACQUIRED = 0;
    NRF_SPIS0_NS->ENABLE = 0;
    spi->slave_state = HAL_SPI_SLAVE_STATE_IDLE;
#endif

    spi->nhs_txbuf = NULL;
    spi->nhs_rxbuf = NULL;
    spi->nhs_buflen = 0;
    spi->nhs_bytes_txd = 0;

    return 0;
}

/**
 * Sets the txrx callback (executed at interrupt context) when the
 * buffer is transferred by the master or the slave using the non-blocking API.
 * Cannot be called when the spi is enabled. This callback will also be called
 * when chip select is de-asserted on the slave.
 *
 * NOTE: This callback is only used for the non-blocking interface and must
 * be called prior to using the non-blocking API.
 *
 * @param spi_num   SPI interface on which to set callback
 * @param txrx      Callback function
 * @param arg       Argument to be passed to callback function
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_set_txrx_cb(int spi_num, hal_spi_txrx_cb txrx_cb, void *arg)
{
    struct nrf5340_net_hal_spi *spi = &nrf5340_net_hal_spi0;

    if (spi_num != 0) {
        return EINVAL;
    }

#if MYNEWT_VAL(SPI_0_MASTER)
    if (NRF_SPIM0_NS->ENABLE != 0) {
        return EINVAL;
    }
#endif

#if MYNEWT_VAL(SPI_0_SLAVE)
    if (NRF_SPIS0_NS->ENABLE != 0) {
        return EINVAL;
    }
#endif

    spi->txrx_cb_func = txrx_cb;
    spi->txrx_cb_arg = arg;

    return 0;
}

/**
 * Non-blocking interface to send a buffer and store received values. Can be
 * used for both master and slave SPI types. The user must configure the
 * callback (using hal_spi_set_txrx_cb); the txrx callback is executed at
 * interrupt context when the buffer is sent.
 *
 * The transmit and receive buffers are either arrays of 8-bit (uint8_t)
 * values or 16-bit values depending on whether the spi is configured for 8 bit
 * data or more than 8 bits per value. The 'cnt' parameter is the number of
 * 8-bit or 16-bit values. Thus, if 'cnt' is 10, txbuf/rxbuf would point to an
 * array of size 10 (in bytes) if the SPI is using 8-bit data; otherwise
 * txbuf/rxbuf would point to an array of size 20 bytes (ten, uint16_t values).
 *
 * NOTE: these buffers are in the native endian-ness of the platform.
 *
 *     MASTER: master sends all the values in the buffer and stores the
 *             stores the values in the receive buffer if rxbuf is not NULL.
 *             The txbuf parameter cannot be NULL
 *     SLAVE: Slave "preloads" the data to be sent to the master (values
 *            stored in txbuf) and places received data from master in rxbuf
 *            (if not NULL). The txrx callback occurs when len values are
 *            transferred or master de-asserts chip select. If txbuf is NULL,
 *            the slave transfers its default byte. Both rxbuf and txbuf cannot
 *            be NULL.
 *
 * @param spi_num   SPI interface to use
 * @param txbuf     Pointer to buffer where values to transmit are stored.
 * @param rxbuf     Pointer to buffer to store values received from peer.
 * @param cnt       Number of 8-bit or 16-bit values to be transferred.
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_txrx_noblock(int spi_num, void *txbuf, void *rxbuf, int len)
{
    struct nrf5340_net_hal_spi *spi = &nrf5340_net_hal_spi0;

    if (spi_num != 0 || (spi->txrx_cb_func == NULL) || (len == 0)) {
        return EINVAL;
    }

#if MYNEWT_VAL(SPI_0_MASTER)
    /* Must have a txbuf for master! */
    if (txbuf == NULL) {
        return EINVAL;
    }

    /* Not allowed if transfer in progress */
    if (spi->spi_xfr_flag) {
        return EBUSY;
    }

    NRF_SPIM0_NS->INTENCLR = SPIM_INTENCLR_END_Msk;
    spi->spi_xfr_flag = 1;

    /* Set internal data structure information */
    spi->nhs_bytes_txd = 0;
    spi->nhs_buflen = len;
    spi->nhs_txbuf = txbuf;

    len = min(SPIM_TXD_MAXCNT_MAX, len);

    /* Set chip registers */
    NRF_SPIM0_NS->TXD.PTR = (uint32_t)txbuf;
    NRF_SPIM0_NS->TXD.MAXCNT = len;

    /* If no rxbuf, we need to set rxbuf and maxcnt to 1 */
    spi->nhs_rxbuf = rxbuf;
    if (rxbuf == NULL) {
        NRF_SPIM0_NS->RXD.PTR = (uint32_t)&spi->dummy_rx;
        NRF_SPIM0_NS->RXD.MAXCNT = 1;
    } else {
        NRF_SPIM0_NS->RXD.PTR = (uint32_t)rxbuf;
        NRF_SPIM0_NS->RXD.MAXCNT = len;
    }

    NRF_SPIM0_NS->EVENTS_END = 0;
    NRF_SPIM0_NS->EVENTS_STOPPED = 0;
    NRF_SPIM0_NS->TASKS_START = 1;
    NRF_SPIM0_NS->INTENSET = SPIM_INTENSET_END_Msk;
#endif

#if MYNEWT_VAL(SPI_0_SLAVE)
    /* Must have txbuf or rxbuf */
    if ((txbuf == NULL) && (rxbuf == NULL)) {
        return EINVAL;
    }

    /* XXX: what to do here? */
    if (len > 255) {
        return EINVAL;
    }

    /*
     * Ready the slave for a transfer. Do not allow this to be called
     * if the slave has already been readied or is requesting the
     * semaphore
     */
    if (spi->slave_state != HAL_SPI_SLAVE_STATE_IDLE) {
        return EBUSY;
    }

    spi->nhs_rxbuf = rxbuf;
    spi->nhs_txbuf = txbuf;
    spi->nhs_buflen = len;
    spi->slave_state = HAL_SPI_SLAVE_STATE_ACQ_SEM;
    NRF_SPIS0_NS->TASKS_ACQUIRE = 1;
#endif

    return 0;
}

/**
 * Sets the default value transferred by the slave. Not valid for master
 *
 * @param spi_num SPI interface to use
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_slave_set_def_tx_val(int spi_num, uint16_t val)
{
#if MYNEWT_VAL(SPI_0_SLAVE)
    if (spi_num != 0){
        return EINVAL;
    }

    NRF_SPIS0_NS->DEF = (uint8_t)val;
    NRF_SPIS0_NS->ORC = (uint8_t)val;

    return 0;
#else
    return EINVAL;
#endif
}

/**
 * This aborts the current transfer but keeps the spi enabled.
 *
 * @param spi_num   SPI interface on which transfer should be aborted.
 *
 * @return int 0 on success, non-zero error code on failure.
 *
 * NOTE: does not return an error if no transfer was in progress.
 */
int
hal_spi_abort(int spi_num)
{
    if (spi_num != 0) {
        return EINVAL;
    }

#if MYNEWT_VAL(SPI_0_MASTER)
    if (nrf5340_net_hal_spi0.spi_xfr_flag) {
        NRF_SPIM0_NS->INTENCLR = NRF_SPI_IRQ_DISABLE_ALL;
        hal_spi_master_stop_transfer();
        nrf5340_net_hal_spi0.spi_xfr_flag = 0;
        NRF_SPIM0_NS->INTENSET = SPIM_INTENSET_END_Msk;
    }
#endif

#if MYNEWT_VAL(SPI_0_SLAVE)
    /* Only way I can see doing this is to disable, then re-enable */
    hal_spi_disable(spi_num);
    hal_spi_enable(spi_num);
#endif

    return 0;
}

#endif
